package org.example.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.example.dao.OrderDOMapper;
import org.example.dataobject.OrderDO;
import org.example.service.ItemService;
import org.example.service.RecommendationService;
import org.example.service.model.ItemModel;
import org.example.service.model.UserSimilarityModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class RecommendationServiceImpl implements RecommendationService {

    @Autowired
    private OrderDOMapper orderDOMapper;

    @Autowired
    private ItemService itemService;
    @Autowired
    private RedisTemplate<String, Object> jsonRedisTemplate;

    // 默认推荐商品数量
    private static final int DEFAULT_RECOMMENDATION_LIMIT = 50;

    // 默认相似用户数量
    private static final int DEFAULT_SIMILAR_USERS_LIMIT = 20;

    // 每批处理的订单数量
    private static final int BATCH_SIZE = 1000;

    // 缓存用户相似度计算结果，key为用户ID，value为相似度列表
    private final Map<Integer, List<UserSimilarityModel>> userSimilarityCache = new ConcurrentHashMap<>();

    // 缓存过期时间（毫秒），设置为1小时
    private static final long CACHE_EXPIRE_TIME_HOURS= 1;

    // 缓存时间戳记录
    private final Map<Integer, Long> cacheTimestamps = new ConcurrentHashMap<>();

    private static final String NEW_ORDER_KEY = "NEW_ORDER_KEY";

    /**
     * 基于用户协同过滤的推荐商品列表
     * @param userId
     * @param limit
     * @return
     */
    @Override
    public List<ItemModel> getRecommendItemsByUserCF(Integer userId, int limit) {
        if (userId == null) {
            return new ArrayList<>();
        }

        // 限制推荐数量
        if (limit <= 0) {
            limit = DEFAULT_RECOMMENDATION_LIMIT;
        }

        // 1. 分批获取所有用户的购买记录
        List<OrderDO> allOrders = getAllOrdersInBatches();

        // 2. 构建用户-商品购买矩阵（带时间权重）
        Map<Integer, Map<Integer, Double>> userItemMap = buildUserItemMapWithTimeWeight(allOrders);

        // 3. 如果目标用户没有购买记录，返回空列表
        if (!userItemMap.containsKey(userId) || userItemMap.get(userId).isEmpty()) {
            return new ArrayList<>();
        }

        // 4. 计算目标用户与其他用户的相似度（使用缓存）
        List<UserSimilarityModel> similarUsers = calculateUserSimilarityWithCache(userId, userItemMap);

        // 5. 获取最相似的用户列表
        List<Integer> topSimilarUsers = similarUsers.stream()
                .limit(DEFAULT_SIMILAR_USERS_LIMIT)
                .map(UserSimilarityModel::getUserId)
                .collect(Collectors.toList());

        // 6. 根据相似用户的购买记录生成推荐列表
        return generateRecommendationsWithDiversity(userId, topSimilarUsers, userItemMap, limit);
    }

    /**
     * 基于用户协同过滤的相似用户列表
     * @param userId
     * @param limit
     * @return
     */
    @Override
    public List<Integer> getSimilarUsers(Integer userId, int limit) {
        if (userId == null) {
            return new ArrayList<>();
        }

        // 限制相似用户数量
        if (limit <= 0) {
            limit = DEFAULT_SIMILAR_USERS_LIMIT;
        }

        // 1. 分批获取所有用户的购买记录
        List<OrderDO> allOrders = getAllOrdersInBatches();

        // 2. 构建用户-商品购买矩阵（带时间权重）
        Map<Integer, Map<Integer, Double>> userItemMap = buildUserItemMapWithTimeWeight(allOrders);

        // 3. 如果目标用户没有购买记录，返回空列表
        if (!userItemMap.containsKey(userId) || userItemMap.get(userId).isEmpty()) {
            return new ArrayList<>();
        }

        // 4. 计算目标用户与其他用户的相似度（使用缓存）
        List<UserSimilarityModel> similarUsers = calculateUserSimilarityWithCache(userId, userItemMap);

        // 5. 返回最相似的用户ID列表
        return similarUsers.stream()
                .limit(limit)
                .map(UserSimilarityModel::getUserId)
                .collect(Collectors.toList());
    }

    /**
     * 分批获取所有订单记录
     * @return 所有订单记录列表
     */
    private List<OrderDO> getAllOrdersInBatches() {
        List<OrderDO> allOrders = new ArrayList<>();
        int offset = 0;
        List<OrderDO> batchOrders;
        LocalDateTime batchStart =  LocalDateTime.now();
        do {
            Map<String,Object> params = new HashMap<>();
            params.put("offset",offset);
            params.put("limit",BATCH_SIZE);
            params.put("data",batchStart);
            /**
             * 分页获取订单记录，其中offset为偏移量，limit为限制数量，data为时间过滤小于等于当前的时间，
             * 仅获取指定时间前的订单记录。
             */
            batchOrders = orderDOMapper.listOrdersByPage(params);
            allOrders.addAll(batchOrders);
            offset += BATCH_SIZE;
        } while (batchOrders.size() == BATCH_SIZE);

        return allOrders;
    }

    /**
     * 构建用户-商品购买矩阵（带时间权重）
     * @param orders 所有订单记录
     * @return 用户-商品购买矩阵
     */
    private Map<Integer, Map<Integer, Double>> buildUserItemMapWithTimeWeight(List<OrderDO> orders) {
        Map<Integer, Map<Integer, Double>> userItemMap = new HashMap<>();

        // 获取最近订单的时间，用于计算时间权重
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime earliestOrderTime = orders.stream()
                .filter(order -> order.getCreateTime() != null)
                .map(OrderDO::getCreateTime)
                .min(Comparator.naturalOrder())
                .orElse(now);

        for (OrderDO order : orders) {
            Integer userId = order.getUserId();
            Integer itemId = order.getItemId();

            // 计算时间权重（最近的订单权重更高）
            double timeWeight = 1.0;
            if (order.getCreateTime() != null) {
                long daysBetween = ChronoUnit.DAYS.between(order.getCreateTime(), now);
                // 时间衰减因子，最近的订单权重更高
                timeWeight = Math.exp(-daysBetween / 30.0); // 30天为半衰期
            }

            // 将用户ID和商品ID添加到矩阵中（带时间权重）
            userItemMap
                    // 确保用户ID对应的映射存在，判断是否为空，为空则创建新的映射
                    .computeIfAbsent(userId, k -> new HashMap<>())
                    // 合并商品ID的时间权重，相同商品ID的权重累加
                    .merge(itemId, timeWeight, Double::sum);
        }

        return userItemMap;
    }

    /**
     * 计算目标用户与其他用户的相似度（使用缓存）
     * @param targetUserId 目标用户ID
     * @param userItemMap 用户-商品购买矩阵
     * @return 用户相似度列表
     */
    private List<UserSimilarityModel> calculateUserSimilarityWithCache(Integer targetUserId, Map<Integer, Map<Integer, Double>> userItemMap) {
        //构建缓存key
        String cacheKey="user_similarity:"+targetUserId;
        String timestampKey="user_similarity_timestamp:"+targetUserId;
        // 检查缓存是否有效,如果缓存过期,则重新计算
        if(isCacheValid(timestampKey)){
            Object cachedData= jsonRedisTemplate.opsForValue().get(cacheKey);
            List<UserSimilarityModel> result = new ArrayList<>();
            for(Object obj:(List<?>) cachedData){
                if(obj instanceof Map){
                    Map<String,Object> map=(Map<String,Object>)obj;
                    Integer userId = (Integer) map.get("userId");
                    BigDecimal similarity = (BigDecimal) map.get("similarity");
                    result.add(new UserSimilarityModel(userId,similarity));
                }
            }
            return result;
        }
        // 缓存无效，重新计算
        List<UserSimilarityModel> similarities = calculateUserSimilarity(targetUserId, userItemMap);

        // 更新缓存
        cacheUserSimilarity(cacheKey,timestampKey,similarities);
        return similarities;
    }
    private void cacheUserSimilarity(String cacheKey,String timestampKey,List<UserSimilarityModel> similarities) {
        // 将相似度列表转换为Redis可存储的格式
        List<Map<String,Object>> serializebleList = new ArrayList<>();
        for(UserSimilarityModel model:similarities){
            Map<String,Object> map = new HashMap<>();
            map.put("userId",model.getUserId());
            map.put("similarity",model.getSimilarity());
            serializebleList.add(map);
        }
        jsonRedisTemplate.opsForValue().set(cacheKey,serializebleList,CACHE_EXPIRE_TIME_HOURS,TimeUnit.HOURS);
        jsonRedisTemplate.opsForValue().set(timestampKey,System.currentTimeMillis(),CACHE_EXPIRE_TIME_HOURS,TimeUnit.HOURS);
    }
    /**
     * 检查缓存是否有效
     * @param userId 用户ID
     * @return 缓存是否有效
     */
    private boolean isCacheValid(String timestampKey) {
        Object timestampObj = jsonRedisTemplate.opsForValue().get(timestampKey);
        if(timestampObj instanceof Long) {
            long cacheTime = (long) timestampObj;
            long currentTime = System.currentTimeMillis();
            return (currentTime-cacheTime) < TimeUnit.HOURS.toMillis(CACHE_EXPIRE_TIME_HOURS);
        }
        return false;
    }

    /**
     * 计算目标用户与其他用户的相似度（带权重）
     * @param targetUserId 目标用户ID
     * @param userItemMap 用户-商品购买矩阵
     * @return 用户相似度列表
     */
    private List<UserSimilarityModel> calculateUserSimilarity(Integer targetUserId,
                                                              Map<Integer, Map<Integer, Double>> userItemMap) {
        List<UserSimilarityModel> similarities = new ArrayList<>();
        Map<Integer, Double> targetUserItems = userItemMap.get(targetUserId);

        // 遍历所有用户，计算与目标用户的相似度
        for (Map.Entry<Integer, Map<Integer, Double>> entry : userItemMap.entrySet()) {
            Integer userId = entry.getKey();

            // 跳过目标用户自身
            if (userId.equals(targetUserId)) {
                continue;
            }

            Map<Integer, Double> userItems = entry.getValue();

            // 计算余弦相似度
            BigDecimal similarity = calculateCosineSimilarityWithWeight(targetUserItems, userItems);

            // 只保留相似度大于0的用户
            if (similarity.compareTo(BigDecimal.ZERO) > 0) {
                similarities.add(new UserSimilarityModel(userId, similarity));
            }
        }

        // 按相似度降序排序
        similarities.sort((a, b) -> b.getSimilarity().compareTo(a.getSimilarity()));

        return similarities;
    }

    /**
     * 计算两个用户之间的余弦相似度（带权重）
     * @param items1 用户1的商品集合
     * @param items2 用户2的商品集合
     * @return 余弦相似度值
     */
    private BigDecimal calculateCosineSimilarityWithWeight(Map<Integer, Double> items1, Map<Integer, Double> items2) {
        // 计算点积
        double dotProduct = 0.0;
        for (Map.Entry<Integer, Double> entry : items1.entrySet()) {
            Integer itemId = entry.getKey();
            if (items2.containsKey(itemId)) {
                dotProduct += entry.getValue() * items2.get(itemId);
            }
        }

        // 计算向量的模
        double norm1 = 0.0;
        double norm2 = 0.0;

        for (Double weight : items1.values()) {
            norm1 += weight * weight;
        }

        for (Double weight : items2.values()) {
            norm2 += weight * weight;
        }

        norm1 = Math.sqrt(norm1);
        norm2 = Math.sqrt(norm2);

        if (norm1 == 0 || norm2 == 0) {
            return BigDecimal.ZERO;
        }

        double similarity = dotProduct / (norm1 * norm2);
        return BigDecimal.valueOf(similarity);
    }

    /**
     * 根据相似用户的购买记录生成推荐列表（增加多样性）
     * @param targetUserId 目标用户ID
     * @param similarUsers 相似用户列表
     * @param userItemMap 用户-商品购买矩阵
     * @param limit 推荐数量限制
     * @return 推荐商品列表
     */
    private List<ItemModel> generateRecommendationsWithDiversity(Integer targetUserId,
                                                                 List<Integer> similarUsers,
                                                                 Map<Integer, Map<Integer, Double>> userItemMap,
                                                                 int limit) {
        // 获取目标用户已购买的商品
        Set<Integer> purchasedItems = userItemMap.get(targetUserId).keySet();

        // 统计相似用户购买的商品及其得分
        Map<Integer, Double> itemScores = new HashMap<>();

        for (Integer userId : similarUsers) {
            Map<Integer, Double> userItems = userItemMap.get(userId);

            // 遍历相似用户购买的商品
            for (Map.Entry<Integer, Double> entry : userItems.entrySet()) {
                Integer itemId = entry.getKey();
                Double weight = entry.getValue();

                // 跳过目标用户已购买的商品
                if (purchasedItems.contains(itemId)) {
                    continue;
                }

                // 累加商品得分（考虑时间权重和用户相似度）
                itemScores.merge(itemId, weight, Double::sum);
            }
        }

        // 按得分降序排序，获取推荐商品ID列表
        List<Integer> recommendedItemIds = itemScores.entrySet().stream()
                .sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())
                .limit(limit * 2) // 取两倍数量用于多样性筛选
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());

        // 增加多样性：从推荐列表中选择多样化的商品
        return selectDiverseItems(recommendedItemIds, limit);
    }

    /**
     * 从推荐列表中选择多样化的商品
     * @param itemIds 商品ID列表
     * @param limit 限制数量
     * @return 多样化的商品列表
     */
    private List<ItemModel> selectDiverseItems(List<Integer> itemIds, int limit) {
        if (itemIds.size() <= limit) {
            // 如果商品数量不超过限制，直接返回所有商品
            return itemIds.stream()
                    .map(itemService::getItemById)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
        }

        List<ItemModel> diverseItems = new ArrayList<>();
        Set<String> selectedCategories = new HashSet<>();

        // 先获取所有商品信息
        Map<Integer, ItemModel> itemMap = new HashMap<>();
        for (Integer itemId : itemIds) {
            ItemModel item = itemService.getItemById(itemId);
            if (item != null) {
                itemMap.put(itemId, item);
            }
        }

        // 优先选择不同类别的商品
        for (Integer itemId : itemIds) {
            ItemModel item = itemMap.get(itemId);
            if (item == null) {
                continue;
            }

            // 简单的多样性策略：基于商品标题的关键词
            String category = extractCategoryFromTitle(item.getTitle());

            // 如果还没有选择过此类别，或者已选择商品数量不足限制数量
            if (!selectedCategories.contains(category) || diverseItems.size() < limit) {
                diverseItems.add(item);
                selectedCategories.add(category);

                // 达到限制数量时停止
                if (diverseItems.size() >= limit) {
                    break;
                }
            }
        }

        return diverseItems;
    }

    /**
     * 从商品标题中提取类别关键词
     * @param title 商品标题
     * @return 类别关键词
     */
    private String extractCategoryFromTitle(String title) {
        if (title == null || title.isEmpty()) {
            return "unknown";
        }

        // 简单的类别提取策略，实际应用中可以使用更复杂的自然语言处理技术
        String[] keywords = {"手机", "电脑", "服装", "鞋子", "书籍", "食品", "家电", "数码"};
        for (String keyword : keywords) {
            if (title.contains(keyword)) {
                return keyword;
            }
        }

        // 如果没有匹配的关键词，返回标题的前三个字符
        return title.length() > 3 ? title.substring(0, 3) : title;
    }




}